﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Configuration.Modules;
using LINQPad.OpenAccess.Helpers;
using Ninject;

namespace LINQPad.OpenAccess.Infrastructure
{
    /// <summary>
    /// <see cref="IServiceLocator"/>
    /// </summary>
    public class NinjectBasedServiceLocator : IServiceLocator
    {
        #region Private

        #region - Vars

        [DebuggerBrowsable( DebuggerBrowsableState.Never )]
        private IKernel _kernel;

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Creates the kernel.
        /// </summary>
        /// <returns>The kernel.</returns>
        protected IKernel CreateKernel( )
        {
            var kernel = new StandardKernel
                                (
                                    new NinjectSettings { LoadExtensions = false }
                                );
            return kernel;
        }
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose( bool disposing )
        {
            if ( disposing )
            {
                if ( this._kernel != null )
                {
                    this._kernel.Dispose( );
                    this._kernel = null;
                }
            }
        }
        /// <summary>
        /// Gets the driver path.
        /// </summary>
        /// <returns>The driver path.</returns>
        protected string GetDriverPath( )
        {
            return Path.GetDirectoryName( typeof( NinjectBasedServiceLocator ).Assembly.Location );
        }
        /// <summary>
        /// Initializes the kernel.
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        protected void InitializeKernel( IKernel kernel )
        {
            kernel.Load<LINQPadOpenAccessModule>( );

            Assembly uiAssembly;
            if ( this.TryLoadAssemblySafely( "LINQPad.OpenAccess.UI.dll", out uiAssembly ) )
                kernel.Load( uiAssembly );

            kernel.Bind<IServiceLocator>( ).ToConstant( this );
        }
        /// <summary>
        /// Tries to safely load the assembly.
        /// </summary>
        /// <param name="assemblyName">Name of the assembly.</param>
        /// <param name="assembly">The assembly.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        protected bool TryLoadAssemblySafely( string assemblyName, out Assembly assembly )
        {
            Guard.ArgumentNotNullOrEmpty( assemblyName, "assemblyName" );
            var fullPath = Path.Combine( GetDriverPath( ), assemblyName );
            if ( !File.Exists( fullPath ) )
            {
                assembly = null;
                return false;
            }
            assembly = DataContextDriver.LoadAssemblySafely( fullPath );
            return true;
        }
        #endregion

        #region - Properties

        /// <summary>
        /// Gets the kernel.
        /// </summary>
        /// <value>
        /// The kernel.
        /// </value>
        protected IKernel Kernel
        {
            get
            {
                return this._kernel;
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="NinjectBasedServiceLocator" /> class.
        /// </summary>
        public NinjectBasedServiceLocator( )
        {
            this._kernel = this.CreateKernel( );
            this.InitializeKernel( this._kernel );
        }

        #endregion

        #endregion

        #region IServiceLocator Members

        /// <summary>
        /// Gets an instance for the specified service type.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns>
        /// The instance.
        /// </returns>
        public object Get( Type serviceType )
        {
            Guard.ArgumentNotNull( serviceType, "serviceType" );
            return this._kernel.Get( serviceType );
        }
        /// <summary>
        /// Gets an instance for the specified type.
        /// </summary>
        /// <typeparam name="T">The type of the instance.</typeparam>
        /// <returns>
        /// The instance.
        /// </returns>
        public T Get<T>( )
        {
            return this._kernel.Get<T>( );
        }
        /// <summary>
        /// Gets all available instances for the specified type.
        /// </summary>
        /// <typeparam name="T">The type of the instance.</typeparam>
        /// <returns>
        /// The instance.
        /// </returns>
        public IEnumerable<T> GetAll<T>( )
        {
            return this._kernel.GetAll<T>( );
        }
        /// <summary>
        /// Injects the dependencies into the specified instance.
        /// </summary>
        /// <param name="instance">The instance.</param>
        public void Inject( object instance )
        {
            Guard.ArgumentNotNull( instance, "instance" );
            this._kernel.Inject( instance );
        }
        /// <summary>
        /// Tries to get an instance for the specified type.
        /// </summary>
        /// <typeparam name="T">The type of the instance.</typeparam>
        /// <returns>
        /// The instance or <c>null</c>.
        /// </returns>
        public T TryGet<T>( )
        {
            return this._kernel.TryGet<T>( );
        }
        /// <summary>
        /// Tries to get an instance for the specified service type.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns>
        /// The instance or <c>null</c>.
        /// </returns>
        public object TryGet( Type serviceType )
        {
            Guard.ArgumentNotNull( serviceType, "serviceType" );

            return this._kernel.TryGet( serviceType );
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose( )
        {
            this.Dispose( true );
            GC.SuppressFinalize( this );
        }

        #endregion
    }
}
